﻿using System;
using System.Collections;
using System.Text;
using TI_BLE_HCI_ClientLib.Controllers.GATT_Entities;
using TI_BLE_HCI_ClientLib.DataEntities.Advertising;
using TI_BLE_HCI_ClientLib.HCICommands;

#if MF_FRAMEWORK_VERSION_V4_3 || MF_FRAMEWORK_VERSION_V4_2
using Microsoft.SPOT;
#endif

namespace TI_BLE_HCI_ClientLib.Controllers.EnhancedControllers
{
    public class GATTServerController : HCISimpleSerialPeripheralProfileController
    {
        const ushort DEFAULT_MTU_SIZE = 23;

        ArrayList _services = new ArrayList();

        Hashtable _characteristicsByValueAttributeHandle = new Hashtable();
        Hashtable _characteristicsByConfigurationAttributeHandle = new Hashtable();
        Hashtable _characteristicsByUserDescriptionAttributeHandle = new Hashtable();
        Hashtable _characteristicsByPresentationFormatAttributeHandle = new Hashtable();
        Hashtable _characteristicsByIdentifier = new Hashtable();

        bool _servicesAlreadyRegistered = false;

        ushort _connectionHandle = 0xFFFF;

        ushort _clientMTUSize = DEFAULT_MTU_SIZE;
        ushort _serverMTUSize = DEFAULT_MTU_SIZE;

        public GATTServerController(string ComPort, int BaudRate)
            : base(ComPort, BaudRate)
        {
            base.LinkEstablished += GATTServerController_LinkEstablished;
            base.LinkTerminated += GATTServerController_LinkTerminated;
            base.ServiceDiscoveryRequestReceived += GATTServerController_ServiceDiscoveryRequestReceived;
            base.ReadServiceAttributesByTypeRequestReceived += GATTServerController_ReadServiceAttributesByTypeRequestReceived;
            base.ReadCharacteristicValueRequestReceived += GATTServerController_ReadCharacteristicValueRequestReceived;
            base.WriteCharacteristicValueRequestReceived += GATTServerController_WriteCharacteristicValueRequestReceived;
            base.ExchangeMTURequestReceived += GATTServerController_ExchangeMTURequestReceived;
        }

        public delegate void CharacteristicNotificationEnabledEventHandler(object s, CharacteristicNotificationOrIndicationEnabledEventArgs e);

        public delegate void CharacteristicValueEventHandler(object s, CharacteristicValueEventArgs e);

        public delegate void LinkStateEventHandler(object s, LinkStateEventArgs e);

        public event CharacteristicNotificationEnabledEventHandler OnCharacteristicNotificationOrIndicationEnabledChanged;

        public event CharacteristicValueEventHandler OnReadCharacteristicValue;

        public event CharacteristicValueEventHandler OnWriteCharacteristicValue;

        public event LinkStateEventHandler OnLinkStateChanged;



        protected ushort ServerMTUSize
        {
            get { return _serverMTUSize; }
            set { _serverMTUSize = value; }
        }
        public GATT_Service AddService(ushort serviceValue)
        {
            var service = new GATT_Service(serviceValue);
            _services.Add(service);

            return service;
        }

        public bool RaiseUncheckedNotification(string characteristicIdentifier, byte[] value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, value);
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, byte[] value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.ByteArray);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, value);
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, byte value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.UInt8);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, value);
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, bool value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Boolean);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, value);
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, string value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.UTF8String);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, value);
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, float value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Float);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, double value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Double);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, int value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Int32);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, uint value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.UInt32);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, short value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Int16);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseNotification(string characteristicIdentifier, ushort value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.UInt16);
                return base.RaiseNotification(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseIndication(string characteristicIdentifier, bool value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Boolean);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, value);
            }

            return false;
        }

        public bool RaiseIndication(string characteristicIdentifier, double value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Double);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseIndication(string characteristicIdentifier, ushort value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.UInt16);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }
        public bool RaiseIndication(string characteristicIdentifier, short value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Int16);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseIndication(string characteristicIdentifier, uint value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.UInt32);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseIndication(string characteristicIdentifier, int value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Int32);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseIndication(string characteristicIdentifier, float value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.Float);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, BitConverter.GetBytes(value));
            }

            return false;
        }

        public bool RaiseIndication(string characteristicIdentifier, string value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.UTF8String);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, value);
            }

            return false;
        }

        public bool RaiseIndication(string characteristicIdentifier, byte value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.UInt8);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, value);
            }

            return false;
        }

        public bool RaiseIndication(string characteristicIdentifier, byte[] value)
        {
            var characteristic = findCharacteristicByIdentifier(characteristicIdentifier);

            if (characteristic != null)
            {
                checkValueType(characteristic, GATT_Characteristic.ValueTypeEnum.ByteArray);
                return base.RaiseIndication(_connectionHandle, characteristic.ValueAttributeHandle, value);
            }

            return false;
        }

        public bool Start(string deviceName, params AdvertisingDataStructure[] additionalAdvertisingDataStructures)
        {
            base.Start();

            if (!Reset(ResetTypeEnum.HardReset)) return false;

            if (!Init()) return false;

            var srData = new AdvertisingAndScanResponseData();

            srData.addAdvertisingDataStructure(new LocalNameAdvertisingDataStructure(AdvertisingDataStructureTypeEnum.CompleteLocalName, deviceName));

            if (!UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.SCANRSPData, srData)) return false;

            var adData = new AdvertisingAndScanResponseData();

            adData.addAdvertisingDataStructure(new FlagsAdvertisingDataStructure(FlagsAdvertisingDataStructure.FlagsEnum.LEGeneralDiscoverableMode | FlagsAdvertisingDataStructure.FlagsEnum.BR_EDR_NotSupported));

            foreach (var ads in additionalAdvertisingDataStructures)
            {
                adData.addAdvertisingDataStructure(ads);
            }

            if (!UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, adData)) return false;

            return true;
        }

        public bool AddAdvertisingDataStructures()
        {
            var adData = new AdvertisingAndScanResponseData();



            return true;
        }

        public bool MakeDiscoverable()
        {
            return MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny);
        }

        public bool RegisterServices()
        {
            if (_servicesAlreadyRegistered) throw new ApplicationException("Services already registered");

            _characteristicsByValueAttributeHandle.Clear();
            _characteristicsByConfigurationAttributeHandle.Clear();
            _characteristicsByUserDescriptionAttributeHandle.Clear();
            _characteristicsByPresentationFormatAttributeHandle.Clear();
            _characteristicsByIdentifier.Clear();

            ushort attributeHandle = 0x0001;

            foreach (GATT_Service service in _services)
            {
                ushort attributeCount = 1;

                foreach (GATT_Characteristic characteristic in service.Characteristics)
                {
                    attributeCount += 4;    // Declaration, Value, UserDescription-Descriptor, PresentationFormat-Descriptor

                    if (((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Notify) != 0) || ((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Indicate) != 0)) attributeCount++;
                }

                ushort startHandle;
                ushort endHandle;

                if (!AddService(service.IsPrimaryService ? HCICommand_GATT_Add_Service.UUIDEnum.PRIMARY_SERVICE : HCICommand_GATT_Add_Service.UUIDEnum.SECONDARY_SERVICE, attributeCount, out startHandle, out endHandle)) return false;

                service.StartHandle = attributeHandle++;

                ushort characteristicType = (ushort)(service.ServiceValue + 1);

                foreach (GATT_Characteristic characteristic in service.Characteristics)
                {
                    HCICommand_GATT_Add_Attribute.PermissionsEnum permissions = HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_NONE;

                    if ((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Read) != 0) permissions |= HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ;
                    if ((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Write) != 0) permissions |= HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_WRITE;

                    if (!AddAttribute(0x2803, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ, out startHandle, out endHandle)) return false;

                    characteristic.DeclarationAttributeHandle = attributeHandle++;

                    // Declaration
                    if (!AddAttribute(characteristicType, permissions, out startHandle, out endHandle)) return false;

                    characteristic.ValueAttributeHandle = attributeHandle++;
                    _characteristicsByValueAttributeHandle[characteristic.ValueAttributeHandle] = characteristic;
                    characteristic.Type = characteristicType;

                    // Configuration
                    if (((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Notify) != 0) || ((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Indicate) != 0))
                    {
                        if (!AddAttribute(0x2902, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ | HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_WRITE,
                            out startHandle, out endHandle)) return false;

                        characteristic.ConfigurationAttributeHandle = attributeHandle++;
                        _characteristicsByConfigurationAttributeHandle[characteristic.ConfigurationAttributeHandle] = characteristic;
                    }

                    // UserDescription-Descriptor
                    if (!AddAttribute(0x2901, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ,
                            out startHandle, out endHandle)) return false;

                    characteristic.UserDescriptionAttributeHandle = attributeHandle++;
                    _characteristicsByUserDescriptionAttributeHandle[characteristic.UserDescriptionAttributeHandle] = characteristic;

                    // PresentationFormat-Descriptor
                    if (!AddAttribute(0x2904, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ,
                            out startHandle, out endHandle)) return false;

                    characteristic.PresentationFormatAttributeHandle = attributeHandle++;
                    _characteristicsByPresentationFormatAttributeHandle[characteristic.PresentationFormatAttributeHandle] = characteristic;

                    _characteristicsByIdentifier[characteristic.Identifier] = characteristic;

                    characteristicType++;
                }

                service.EndHandle = (ushort)(attributeHandle - 1);
            }

            _servicesAlreadyRegistered = true;

            return true;
        }

        void GATTServerController_ExchangeMTURequestReceived(HCIEvents.HCIEvent_ATT_Exchange_MTU_Request evt)
        {
            _clientMTUSize = evt.ClientReceiveMTUSize;

            if (!base.RespondToExchangeMTURequest(evt.ConnHandle, _serverMTUSize))
            {
                throw new ApplicationException("RespondToExchangeMTURequest FAILED");
            }
        }

        void GATTServerController_LinkEstablished(HCIEvents.HCIEvent_GAP_Link_Established evt)
        {
            _connectionHandle = evt.ConnHandle;

            if (OnLinkStateChanged != null)
            {
                OnLinkStateChanged(this, new LinkStateEventArgs(true));
            }
        }

        void GATTServerController_LinkTerminated(HCIEvents.HCIEvent_GAP_Link_Terminated evt)
        {
            _connectionHandle = 0xFFFF;

            if (OnLinkStateChanged != null)
            {
                OnLinkStateChanged(this, new LinkStateEventArgs(false));
            }
        }
        void checkValueType(GATT_Characteristic characteristic, GATT_Characteristic.ValueTypeEnum valueType)
        {
            if (characteristic.ValueType != valueType) throw new ApplicationException("Wrong characteristic value type");
        }
        void GATTServerController_ReadServiceAttributesByTypeRequestReceived(HCIEvents.HCIEvent_ATT_Read_By_Type_Request evt)
        {
            if (evt.Type != 0x2803) return;

            ArrayList attributeDataPage = new ArrayList();

            foreach (GATT_Service service in _services)
            {
                foreach (GATT_Characteristic characteristic in service.Characteristics)
                {
                    if (characteristic.DeclarationAttributeHandle >= evt.StartHandle && characteristic.DeclarationAttributeHandle <= evt.EndHandle)
                    {
                        attributeDataPage.Add(
                            new HCICommand_GATT_Read_By_Type_Response.AttributeData(characteristic.DeclarationAttributeHandle,
                                (((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Read) != 0) ? HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Read : 0) |
                                (((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Write) != 0) ? HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Write : 0) |
                                (((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Notify) != 0) ? HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Notify : 0) |
                                (((characteristic.Permissions & GATT_Characteristic.PermissionsEnum.Indicate) != 0) ? HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Indicate : 0),
                                characteristic.ValueAttributeHandle,
                                characteristic.Type));

                        if (attributeDataPage.Count == 3)
                        {
                            RespondToReadServiceAttributesByTypeRequest(evt.ConnHandle, attributeDataPage.ToArray(typeof(HCICommand_GATT_Read_By_Type_Response.AttributeData)) as HCICommand_GATT_Read_By_Type_Response.AttributeData[]);
                            return;
                        }
                    }
                }

                if (attributeDataPage.Count != 0)
                {
                    RespondToReadServiceAttributesByTypeRequest(evt.ConnHandle, attributeDataPage.ToArray(typeof(HCICommand_GATT_Read_By_Type_Response.AttributeData)) as HCICommand_GATT_Read_By_Type_Response.AttributeData[]);
                    return;
                }
            }

            SendErrorResponse(evt.ConnHandle, HCICommand_GATT_Error_Response.RequestOpCodeEnum.ReadRequest, evt.StartHandle, HCICommand_GATT_Error_Response.ErrorCodeEnum.AttributeHandleNotValid);
        }

        void GATTServerController_ServiceDiscoveryRequestReceived(HCIEvents.HCIEvent_ATT_Read_By_Group_Type_Request evt)
        {
            var attributeGroupDataArray = new HCICommand_GATT_Read_By_Group_Type_Response.AttributeGroupData[_services.Count];

            for (int i = 0; i < _services.Count; i++)
            {
                GATT_Service service = _services[i] as GATT_Service;

                attributeGroupDataArray[i] = new HCICommand_GATT_Read_By_Group_Type_Response.AttributeGroupData(service.StartHandle,
                    ((i == _services.Count - 1) ? (ushort)0xFFFF : service.EndHandle),
                    service.ServiceValue);
            }

            RespondToServiceDiscoveryRequest(evt.ConnHandle, attributeGroupDataArray);
        }

        GATT_Characteristic findCharacteristicByValueAttributeHandle(ushort handle)
        {
            if (!_characteristicsByValueAttributeHandle.Contains(handle)) return null;

            return _characteristicsByValueAttributeHandle[handle] as GATT_Characteristic;
        }

        GATT_Characteristic findCharacteristicByConfigurationAttributeHandle(ushort handle)
        {
            if (!_characteristicsByConfigurationAttributeHandle.Contains(handle)) return null;

            return _characteristicsByConfigurationAttributeHandle[handle] as GATT_Characteristic;
        }

        GATT_Characteristic findCharacteristicByUserDescriptionAttributeHandle(ushort handle)
        {
            if (!_characteristicsByUserDescriptionAttributeHandle.Contains(handle)) return null;

            return _characteristicsByUserDescriptionAttributeHandle[handle] as GATT_Characteristic;
        }

        GATT_Characteristic findCharacteristicByPresentationFormatAttributeHandle(ushort handle)
        {
            if (!_characteristicsByPresentationFormatAttributeHandle.Contains(handle)) return null;

            return _characteristicsByPresentationFormatAttributeHandle[handle] as GATT_Characteristic;
        }

        GATT_Characteristic findCharacteristicByIdentifier(string identifier)
        {
            if (!_characteristicsByIdentifier.Contains(identifier)) return null;

            return _characteristicsByIdentifier[identifier] as GATT_Characteristic;
        }

        void GATTServerController_ReadCharacteristicValueRequestReceived(HCIEvents.HCIEvent_ATT_Read_Characteristic_Value_Request evt)
        {
            var characteristic = findCharacteristicByValueAttributeHandle(evt.AttributeHandle);

            if (characteristic != null)
            {
                RespondToReadCharacteristicValueRequest(evt.ConnHandle, onReadCharacteristicValue(characteristic));
                return;
            }

            characteristic = findCharacteristicByConfigurationAttributeHandle(evt.AttributeHandle);

            if (characteristic != null)
            {
                RespondToReadCharacteristicValueRequest(evt.ConnHandle, (ushort)
                    ((characteristic.NotificationEnabled ? GATT_Characteristic.CharacteristicConfigurationFlagsEnum.notification_enabled : 0) |
                    (characteristic.NotificationEnabled ? GATT_Characteristic.CharacteristicConfigurationFlagsEnum.indication_enabled : 0)));
                return;
            }

            characteristic = findCharacteristicByUserDescriptionAttributeHandle(evt.AttributeHandle);

            if (characteristic != null)
            {
                RespondToReadCharacteristicValueRequest(evt.ConnHandle, characteristic.UserDescription);
                return;
            }

            characteristic = findCharacteristicByPresentationFormatAttributeHandle(evt.AttributeHandle);

            if (characteristic != null)
            {
                RespondToReadCharacteristicValueRequest(evt.ConnHandle, characteristic.PresentationFormat.ToByteArray());
                return;
            }

            SendErrorResponse(evt.ConnHandle, HCICommand_GATT_Error_Response.RequestOpCodeEnum.ReadRequest, evt.AttributeHandle, HCICommand_GATT_Error_Response.ErrorCodeEnum.AttributeNotFound);
        }
        void GATTServerController_WriteCharacteristicValueRequestReceived(HCIEvents.HCIEvent_ATT_Write_Characteristic_Value_Request evt)
        {
            var characteristic = findCharacteristicByValueAttributeHandle(evt.AttributeHandle);

            if (characteristic != null)
            {
                onWriteCharacteristicValue(characteristic, evt.Value);
                RespondToWriteCharacteristicValueRequest(evt.ConnHandle);
                return;
            }

            characteristic = findCharacteristicByConfigurationAttributeHandle(evt.AttributeHandle);

            if (characteristic != null)
            {
                characteristic.NotificationEnabled = ((evt.Value[0] & (byte)GATT_Characteristic.CharacteristicConfigurationFlagsEnum.notification_enabled) != 0);
                characteristic.IndicationEnabled = ((evt.Value[0] & (byte)GATT_Characteristic.CharacteristicConfigurationFlagsEnum.indication_enabled) != 0);
                onNotificationEnabledChanged(characteristic, characteristic.NotificationEnabled, characteristic.IndicationEnabled);
                RespondToWriteCharacteristicValueRequest(evt.ConnHandle);
                return;
            }

            SendErrorResponse(evt.ConnHandle, HCICommand_GATT_Error_Response.RequestOpCodeEnum.WriteRequest, evt.AttributeHandle, HCICommand_GATT_Error_Response.ErrorCodeEnum.AttributeNotFound);
        }

        void onNotificationEnabledChanged(GATT_Characteristic characteristic, bool notificationEnabled, bool indicationEnabled)
        {
            if (OnCharacteristicNotificationOrIndicationEnabledChanged != null)
            {
                var e = new CharacteristicNotificationOrIndicationEnabledEventArgs(characteristic, notificationEnabled, indicationEnabled);

                OnCharacteristicNotificationOrIndicationEnabledChanged(this, e);
            }
        }

        byte[] onReadCharacteristicValue(GATT_Characteristic characteristic)
        {
            if (OnReadCharacteristicValue != null)
            {
                var e = new CharacteristicValueEventArgs(characteristic);

                OnReadCharacteristicValue(this, e);

                return e.Value;
            }

            return null;
        }

        void onWriteCharacteristicValue(GATT_Characteristic characteristic, byte[] value)
        {
            if (OnWriteCharacteristicValue != null)
            {
                var e = new CharacteristicValueEventArgs(characteristic, value);

                OnWriteCharacteristicValue(this, e);
            }
        }

        public class CharacteristicNotificationOrIndicationEnabledEventArgs : EventArgs
        {
            GATT_Characteristic _characteristic;
            bool _notificationState;
            bool _indicationState;

            public CharacteristicNotificationOrIndicationEnabledEventArgs(GATT_Characteristic characteristic, bool notificationState, bool indicationState)
            {
                _characteristic = characteristic;
                _notificationState = notificationState;
                _indicationState = indicationState;
            }

            public GATT_Characteristic Characteristic
            {
                get { return _characteristic; }
            }
            public bool NotificationState
            {
                get { return _notificationState; }
                set { _notificationState = value; }
            }

            public bool IndicationState
            {
                get { return _indicationState; }
                set { _indicationState = value; }
            }
        }

        public class LinkStateEventArgs : EventArgs
        {
            bool _state;

            public LinkStateEventArgs(bool state)
            {
                _state = state;
            }

            public bool State
            {
                get { return _state; }
            }
        }

        public class CharacteristicValueEventArgs : EventArgs
        {
            GATT_Characteristic _characteristic;
            byte[] _value;


            public CharacteristicValueEventArgs(GATT_Characteristic characteristic)
            {
                _characteristic = characteristic;
            }

            public CharacteristicValueEventArgs(GATT_Characteristic characteristic, byte[] value)
                : this(characteristic)
            {
                _value = value;
            }

            public GATT_Characteristic Characteristic
            {
                get { return _characteristic; }
            }

            public byte[] Value
            {
                get { return _value; }
                set { _value = value; }
            }

            public byte[] GetValue()
            {
                return _value;
            }
            public void SetValue(byte[] value)
            {
                _value = value;
            }

            public byte[] GetValueAsByteArray()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.ByteArray);

                return _value;
            }

            public void SetValueAsByteArray(byte[] value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.ByteArray);

                _value = value;
            }

            public byte GetValueAsByte()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.UInt8);

                return _value[0];
            }

            public void SetValue(byte value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.UInt8);

                _value = new byte[] { value };
            }

            public sbyte GetValueAsSignedByte()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Int8);

                return (sbyte)_value[0];
            }

            public void SetValue(sbyte value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Int8);

                _value = new byte[] { (byte)value };
            }

            public bool GetValueAsBoolean()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Boolean);

                return _value[0] != 0x00;
            }

            public void SetValue(bool value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Boolean);

                _value = new byte[] { (byte)(value ? 0x01 : 0x00), 0x00 };
            }

            public string GetValueAsString()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.UTF8String);

                return new string(Encoding.UTF8.GetChars(_value));
            }

            public void SetValue(string value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.UTF8String);

                _value = Encoding.UTF8.GetBytes(value);
            }

            public float GetValueAsSingle()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Float);

                return BitConverter.ToSingle(_value, 0);
            }

            public void SetValue(float value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Float);

                _value = BitConverter.GetBytes(value);
            }

            public double GetValueAsDouble()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Double);

                return BitConverter.ToDouble(_value, 0);
            }

            public void SetValue(double value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Double);

                _value = BitConverter.GetBytes(value);
            }

            public double GetValueAsInt32()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Int32);

                return BitConverter.ToInt32(_value, 0);
            }

            public void SetValue(int value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Int32);

                _value = BitConverter.GetBytes(value);
            }

            public double GetValueAsUInt32()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.UInt32);

                return BitConverter.ToUInt32(_value, 0);
            }

            public void SetValue(uint value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.UInt32);

                _value = BitConverter.GetBytes(value);
            }

            public double GetValueAsInt16()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Int16);

                return BitConverter.ToInt16(_value, 0);
            }

            public void SetValue(short value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.Int16);

                _value = BitConverter.GetBytes(value);
            }

            public double GetValueAsUInt16()
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.UInt16);

                return BitConverter.ToUInt16(_value, 0);
            }

            public void SetValue(ushort value)
            {
                checkValueType(GATT_Characteristic.ValueTypeEnum.UInt16);

                _value = BitConverter.GetBytes(value);
            }

            void checkValueType(GATT_Characteristic.ValueTypeEnum valueType)
            {
                if (_characteristic.ValueType != valueType) throw new ApplicationException("Wrong characteristic value type");
            }
        }
    }
}
